/*
YUI 3.17.2 (build 9c3c78e)
Copyright 2014 Yahoo! Inc. All rights reserved.
Licensed under the BSD License.
http://yuilibrary.com/license/
*/

YUI.add('charts-legend', function (Y, NAME) {

/**
 * Adds legend functionality to charts.
 *
 * @module charts
 * @submodule charts-legend
 */
var TOP = "top",
RIGHT = "right",
BOTTOM = "bottom",
LEFT = "left",
EXTERNAL = "external",
HORIZONTAL = "horizontal",
VERTICAL = "vertical",
WIDTH = "width",
HEIGHT = "height",
POSITION = "position",
_X = "x",
_Y = "y",
PX = "px",
PieChartLegend,
LEGEND = {
    setter: function(val)
    {
        var legend = this.get("legend");
        if(legend)
        {
            legend.destroy(true);
        }
        if(val instanceof Y.ChartLegend)
        {
            legend = val;
            legend.set("chart", this);
        }
        else
        {
            val.chart = this;
            if(!val.hasOwnProperty("render"))
            {
                val.render = this.get("contentBox");
                val.includeInChartLayout = true;
            }
            legend = new Y.ChartLegend(val);
        }
        return legend;
    }
},

/**
 * Contains methods for displaying items horizontally in a legend.
 *
 * @module charts
 * @submodule charts-legend
 * @class HorizontalLegendLayout
 */
HorizontalLegendLayout = {
    /**
     * Displays items horizontally in a legend.
     *
     * @method _positionLegendItems
     * @param {Array} items Array of items to display in the legend.
     * @param {Number} maxWidth The width of the largest item in the legend.
     * @param {Number} maxHeight The height of the largest item in the legend.
     * @param {Number} totalWidth The total width of all items in a legend.
     * @param {Number} totalHeight The total height of all items in a legend.
     * @param {Number} padding The left, top, right and bottom padding properties for the legend.
     * @param {Number} horizontalGap The horizontal distance between items in a legend.
     * @param {Number} verticalGap The vertical distance between items in a legend.
     * @param {String} hAlign The horizontal alignment of the legend.
     * @protected
     */
    _positionLegendItems: function(items, maxWidth, maxHeight, totalWidth, totalHeight, padding, horizontalGap, verticalGap, hAlign)
    {
        var i = 0,
            rowIterator = 0,
            item,
            node,
            itemWidth,
            itemHeight,
            len,
            width = this.get("width"),
            rows,
            rowsLen,
            row,
            totalWidthArray,
            legendWidth,
            topHeight = padding.top - verticalGap,
            limit = width - (padding.left + padding.right),
            left,
            top,
            right,
            bottom;
        HorizontalLegendLayout._setRowArrays(items, limit, horizontalGap);
        rows = HorizontalLegendLayout.rowArray;
        totalWidthArray = HorizontalLegendLayout.totalWidthArray;
        rowsLen = rows.length;
        for(; rowIterator < rowsLen; ++ rowIterator)
        {
            topHeight += verticalGap;
            row = rows[rowIterator];
            len = row.length;
            legendWidth =  HorizontalLegendLayout.getStartPoint(width, totalWidthArray[rowIterator], hAlign, padding);
            for(i = 0; i < len; ++i)
            {
                item = row[i];
                node = item.node;
                itemWidth = item.width;
                itemHeight = item.height;
                item.x = legendWidth;
                item.y = 0;
                left = !isNaN(left) ? Math.min(left, legendWidth) : legendWidth;
                top = !isNaN(top) ? Math.min(top, topHeight) : topHeight;
                right = !isNaN(right) ? Math.max(legendWidth + itemWidth, right) : legendWidth + itemWidth;
                bottom = !isNaN(bottom) ? Math.max(topHeight + itemHeight, bottom) : topHeight + itemHeight;
                node.setStyle("left", legendWidth + PX);
                node.setStyle("top", topHeight + PX);
                legendWidth += itemWidth + horizontalGap;
            }
            topHeight += item.height;
        }
        this._contentRect = {
            left: left,
            top: top,
            right: right,
            bottom: bottom
        };
        if(this.get("includeInChartLayout"))
        {
            this.set("height", topHeight + padding.bottom);
        }
    },

    /**
     * Creates row and total width arrays used for displaying multiple rows of
     * legend items based on the items, available width and horizontalGap for the legend.
     *
     * @method _setRowArrays
     * @param {Array} items Array of legend items to display in a legend.
     * @param {Number} limit Total available width for displaying items in a legend.
     * @param {Number} horizontalGap Horizontal distance between items in a legend.
     * @protected
     */
    _setRowArrays: function(items, limit, horizontalGap)
    {
        var item = items[0],
            rowArray = [[item]],
            i = 1,
            rowIterator = 0,
            len = items.length,
            totalWidth = item.width,
            itemWidth,
            totalWidthArray = [[totalWidth]];
        for(; i < len; ++i)
        {
            item = items[i];
            itemWidth = item.width;
            if((totalWidth + horizontalGap + itemWidth) <= limit)
            {
                totalWidth += horizontalGap + itemWidth;
                rowArray[rowIterator].push(item);
            }
            else
            {
                totalWidth = horizontalGap + itemWidth;
                if(rowArray[rowIterator])
                {
                    rowIterator += 1;
                }
                rowArray[rowIterator] = [item];
            }
            totalWidthArray[rowIterator] = totalWidth;
        }
        HorizontalLegendLayout.rowArray = rowArray;
        HorizontalLegendLayout.totalWidthArray = totalWidthArray;
    },

    /**
     * Returns the starting x-coordinate for a row of legend items.
     *
     * @method getStartPoint
     * @param {Number} w Width of the legend.
     * @param {Number} totalWidth Total width of all labels in the row.
     * @param {String} align Horizontal alignment of items for the legend.
     * @param {Object} padding Object contain left, top, right and bottom padding properties.
     * @return Number
     * @protected
     */
    getStartPoint: function(w, totalWidth, align, padding)
    {
        var startPoint;
        switch(align)
        {
            case LEFT :
                startPoint = padding.left;
            break;
            case "center" :
                startPoint = (w - totalWidth) * 0.5;
            break;
            case RIGHT :
                startPoint = w - totalWidth - padding.right;
            break;
        }
        return startPoint;
    }
},

/**
 * Contains methods for displaying items vertically in a legend.
 *
 * @module charts
 * @submodule charts-legend
 * @class VerticalLegendLayout
 */
VerticalLegendLayout = {
    /**
     * Displays items vertically in a legend.
     *
     * @method _positionLegendItems
     * @param {Array} items Array of items to display in the legend.
     * @param {Number} maxWidth The width of the largest item in the legend.
     * @param {Number} maxHeight The height of the largest item in the legend.
     * @param {Number} totalWidth The total width of all items in a legend.
     * @param {Number} totalHeight The total height of all items in a legend.
     * @param {Number} padding The left, top, right and bottom padding properties for the legend.
     * @param {Number} horizontalGap The horizontal distance between items in a legend.
     * @param {Number} verticalGap The vertical distance between items in a legend.
     * @param {String} vAlign The vertical alignment of the legend.
     * @protected
     */
    _positionLegendItems: function(items, maxWidth, maxHeight, totalWidth, totalHeight, padding, horizontalGap, verticalGap, vAlign)
    {
        var i = 0,
            columnIterator = 0,
            item,
            node,
            itemHeight,
            itemWidth,
            len,
            height = this.get("height"),
            columns,
            columnsLen,
            column,
            totalHeightArray,
            legendHeight,
            leftWidth = padding.left - horizontalGap,
            legendWidth,
            limit = height - (padding.top + padding.bottom),
            left,
            top,
            right,
            bottom;
        VerticalLegendLayout._setColumnArrays(items, limit, verticalGap);
        columns = VerticalLegendLayout.columnArray;
        totalHeightArray = VerticalLegendLayout.totalHeightArray;
        columnsLen = columns.length;
        for(; columnIterator < columnsLen; ++ columnIterator)
        {
            leftWidth += horizontalGap;
            column = columns[columnIterator];
            len = column.length;
            legendHeight =  VerticalLegendLayout.getStartPoint(height, totalHeightArray[columnIterator], vAlign, padding);
            legendWidth = 0;
            for(i = 0; i < len; ++i)
            {
                item = column[i];
                node = item.node;
                itemHeight = item.height;
                itemWidth = item.width;
                item.y = legendHeight;
                item.x = leftWidth;
                left = !isNaN(left) ? Math.min(left, leftWidth) : leftWidth;
                top = !isNaN(top) ? Math.min(top, legendHeight) : legendHeight;
                right = !isNaN(right) ? Math.max(leftWidth + itemWidth, right) : leftWidth + itemWidth;
                bottom = !isNaN(bottom) ? Math.max(legendHeight + itemHeight, bottom) : legendHeight + itemHeight;
                node.setStyle("left", leftWidth + PX);
                node.setStyle("top", legendHeight + PX);
                legendHeight += itemHeight + verticalGap;
                legendWidth = Math.max(legendWidth, item.width);
            }
            leftWidth += legendWidth;
        }
        this._contentRect = {
            left: left,
            top: top,
            right: right,
            bottom: bottom
        };
        if(this.get("includeInChartLayout"))
        {
            this.set("width", leftWidth + padding.right);
        }
    },

    /**
     * Creates column and total height arrays used for displaying multiple columns of
     * legend items based on the items, available height and verticalGap for the legend.
     *
     * @method _setColumnArrays
     * @param {Array} items Array of legend items to display in a legend.
     * @param {Number} limit Total available height for displaying items in a legend.
     * @param {Number} verticalGap Vertical distance between items in a legend.
     * @protected
     */
    _setColumnArrays: function(items, limit, verticalGap)
    {
        var item = items[0],
            columnArray = [[item]],
            i = 1,
            columnIterator = 0,
            len = items.length,
            totalHeight = item.height,
            itemHeight,
            totalHeightArray = [[totalHeight]];
        for(; i < len; ++i)
        {
            item = items[i];
            itemHeight = item.height;
            if((totalHeight + verticalGap + itemHeight) <= limit)
            {
                totalHeight += verticalGap + itemHeight;
                columnArray[columnIterator].push(item);
            }
            else
            {
                totalHeight = verticalGap + itemHeight;
                if(columnArray[columnIterator])
                {
                    columnIterator += 1;
                }
                columnArray[columnIterator] = [item];
            }
            totalHeightArray[columnIterator] = totalHeight;
        }
        VerticalLegendLayout.columnArray = columnArray;
        VerticalLegendLayout.totalHeightArray = totalHeightArray;
    },

    /**
     * Returns the starting y-coordinate for a column of legend items.
     *
     * @method getStartPoint
     * @param {Number} h Height of the legend.
     * @param {Number} totalHeight Total height of all labels in the column.
     * @param {String} align Vertical alignment of items for the legend.
     * @param {Object} padding Object contain left, top, right and bottom padding properties.
     * @return Number
     * @protected
     */
    getStartPoint: function(h, totalHeight, align, padding)
    {
        var startPoint;
        switch(align)
        {
            case TOP :
                startPoint = padding.top;
            break;
            case "middle" :
                startPoint = (h - totalHeight) * 0.5;
            break;
            case BOTTOM :
                startPoint = h - totalHeight - padding.bottom;
            break;
        }
        return startPoint;
    }
},

CartesianChartLegend = Y.Base.create("cartesianChartLegend", Y.CartesianChart, [], {
    /**
     * Redraws and position all the components of the chart instance.
     *
     * @method _redraw
     * @private
     */
    _redraw: function()
    {
        if(this._drawing)
        {
            this._callLater = true;
            return;
        }
        this._drawing = true;
        this._callLater = false;
        var w = this.get("width"),
            h = this.get("height"),
            layoutBoxDimensions = this._getLayoutBoxDimensions(),
            leftPaneWidth = layoutBoxDimensions.left,
            rightPaneWidth = layoutBoxDimensions.right,
            topPaneHeight = layoutBoxDimensions.top,
            bottomPaneHeight = layoutBoxDimensions.bottom,
            leftAxesCollection = this.get("leftAxesCollection"),
            rightAxesCollection = this.get("rightAxesCollection"),
            topAxesCollection = this.get("topAxesCollection"),
            bottomAxesCollection = this.get("bottomAxesCollection"),
            i = 0,
            l,
            axis,
            graphOverflow = "visible",
            graph = this.get("graph"),
            topOverflow,
            bottomOverflow,
            leftOverflow,
            rightOverflow,
            graphWidth,
            graphHeight,
            graphX,
            graphY,
            allowContentOverflow = this.get("allowContentOverflow"),
            diff,
            rightAxesXCoords,
            leftAxesXCoords,
            topAxesYCoords,
            bottomAxesYCoords,
            legend = this.get("legend"),
            graphRect = {};

        if(leftAxesCollection)
        {
            leftAxesXCoords = [];
            l = leftAxesCollection.length;
            for(i = l - 1; i > -1; --i)
            {
                leftAxesXCoords.unshift(leftPaneWidth);
                leftPaneWidth += leftAxesCollection[i].get("width");
            }
        }
        if(rightAxesCollection)
        {
            rightAxesXCoords = [];
            l = rightAxesCollection.length;
            i = 0;
            for(i = l - 1; i > -1; --i)
            {
                rightPaneWidth += rightAxesCollection[i].get("width");
                rightAxesXCoords.unshift(w - rightPaneWidth);
            }
        }
        if(topAxesCollection)
        {
            topAxesYCoords = [];
            l = topAxesCollection.length;
            for(i = l - 1; i > -1; --i)
            {
                topAxesYCoords.unshift(topPaneHeight);
                topPaneHeight += topAxesCollection[i].get("height");
            }
        }
        if(bottomAxesCollection)
        {
            bottomAxesYCoords = [];
            l = bottomAxesCollection.length;
            for(i = l - 1; i > -1; --i)
            {
                bottomPaneHeight += bottomAxesCollection[i].get("height");
                bottomAxesYCoords.unshift(h - bottomPaneHeight);
            }
        }

        graphWidth = w - (leftPaneWidth + rightPaneWidth);
        graphHeight = h - (bottomPaneHeight + topPaneHeight);
        graphRect.left = leftPaneWidth;
        graphRect.top = topPaneHeight;
        graphRect.bottom = h - bottomPaneHeight;
        graphRect.right = w - rightPaneWidth;
        if(!allowContentOverflow)
        {
            topOverflow = this._getTopOverflow(leftAxesCollection, rightAxesCollection);
            bottomOverflow = this._getBottomOverflow(leftAxesCollection, rightAxesCollection);
            leftOverflow = this._getLeftOverflow(bottomAxesCollection, topAxesCollection);
            rightOverflow = this._getRightOverflow(bottomAxesCollection, topAxesCollection);

            diff = topOverflow - topPaneHeight;
            if(diff > 0)
            {
                graphRect.top = topOverflow;
                if(topAxesYCoords)
                {
                    i = 0;
                    l = topAxesYCoords.length;
                    for(; i < l; ++i)
                    {
                        topAxesYCoords[i] += diff;
                    }
                }
            }

            diff = bottomOverflow - bottomPaneHeight;
            if(diff > 0)
            {
                graphRect.bottom = h - bottomOverflow;
                if(bottomAxesYCoords)
                {
                    i = 0;
                    l = bottomAxesYCoords.length;
                    for(; i < l; ++i)
                    {
                        bottomAxesYCoords[i] -= diff;
                    }
                }
            }

            diff = leftOverflow - leftPaneWidth;
            if(diff > 0)
            {
                graphRect.left = leftOverflow;
                if(leftAxesXCoords)
                {
                    i = 0;
                    l = leftAxesXCoords.length;
                    for(; i < l; ++i)
                    {
                        leftAxesXCoords[i] += diff;
                    }
                }
            }

            diff = rightOverflow - rightPaneWidth;
            if(diff > 0)
            {
                graphRect.right = w - rightOverflow;
                if(rightAxesXCoords)
                {
                    i = 0;
                    l = rightAxesXCoords.length;
                    for(; i < l; ++i)
                    {
                        rightAxesXCoords[i] -= diff;
                    }
                }
            }
        }
        graphWidth = graphRect.right - graphRect.left;
        graphHeight = graphRect.bottom - graphRect.top;
        graphX = graphRect.left;
        graphY = graphRect.top;
        if(legend)
        {
            if(legend.get("includeInChartLayout"))
            {
                switch(legend.get("position"))
                {
                    case "left" :
                        legend.set("y", graphY);
                        legend.set("height", graphHeight);
                    break;
                    case "top" :
                        legend.set("x", graphX);
                        legend.set("width", graphWidth);
                    break;
                    case "bottom" :
                        legend.set("x", graphX);
                        legend.set("width", graphWidth);
                    break;
                    case "right" :
                        legend.set("y", graphY);
                        legend.set("height", graphHeight);
                    break;
                }
            }
        }
        if(topAxesCollection)
        {
            l = topAxesCollection.length;
            i = 0;
            for(; i < l; i++)
            {
                axis = topAxesCollection[i];
                if(axis.get("width") !== graphWidth)
                {
                    axis.set("width", graphWidth);
                }
                axis.get("boundingBox").setStyle("left", graphX + PX);
                axis.get("boundingBox").setStyle("top", topAxesYCoords[i] + PX);
            }
            if(axis._hasDataOverflow())
            {
                graphOverflow = "hidden";
            }
        }
        if(bottomAxesCollection)
        {
            l = bottomAxesCollection.length;
            i = 0;
            for(; i < l; i++)
            {
                axis = bottomAxesCollection[i];
                if(axis.get("width") !== graphWidth)
                {
                    axis.set("width", graphWidth);
                }
                axis.get("boundingBox").setStyle("left", graphX + PX);
                axis.get("boundingBox").setStyle("top", bottomAxesYCoords[i] + PX);
            }
            if(axis._hasDataOverflow())
            {
                graphOverflow = "hidden";
            }
        }
        if(leftAxesCollection)
        {
            l = leftAxesCollection.length;
            i = 0;
            for(; i < l; ++i)
            {
                axis = leftAxesCollection[i];
                axis.get("boundingBox").setStyle("top", graphY + PX);
                axis.get("boundingBox").setStyle("left", leftAxesXCoords[i] + PX);
                if(axis.get("height") !== graphHeight)
                {
                    axis.set("height", graphHeight);
                }
            }
            if(axis._hasDataOverflow())
            {
                graphOverflow = "hidden";
            }
        }
        if(rightAxesCollection)
        {
            l = rightAxesCollection.length;
            i = 0;
            for(; i < l; ++i)
            {
                axis = rightAxesCollection[i];
                axis.get("boundingBox").setStyle("top", graphY + PX);
                axis.get("boundingBox").setStyle("left", rightAxesXCoords[i] + PX);
                if(axis.get("height") !== graphHeight)
                {
                    axis.set("height", graphHeight);
                }
            }
            if(axis._hasDataOverflow())
            {
                graphOverflow = "hidden";
            }
        }
        this._drawing = false;
        if(this._callLater)
        {
            this._redraw();
            return;
        }
        if(graph)
        {
            graph.get("boundingBox").setStyle("left", graphX + PX);
            graph.get("boundingBox").setStyle("top", graphY + PX);
            graph.set("width", graphWidth);
            graph.set("height", graphHeight);
            graph.get("boundingBox").setStyle("overflow", graphOverflow);
        }

        if(this._overlay)
        {
            this._overlay.setStyle("left", graphX + PX);
            this._overlay.setStyle("top", graphY + PX);
            this._overlay.setStyle("width", graphWidth + PX);
            this._overlay.setStyle("height", graphHeight + PX);
        }
    },

    /**
     * Positions the legend in a chart and returns the properties of the legend to be used in the
     * chart's layout algorithm.
     *
     * @method _getLayoutDimensions
     * @return {Object} The left, top, right and bottom values for the legend.
     * @protected
     */
    _getLayoutBoxDimensions: function()
    {
        var box = {
                top: 0,
                right: 0,
                bottom: 0,
                left: 0
            },
            legend = this.get("legend"),
            position,
            direction,
            dimension,
            size,
            w = this.get(WIDTH),
            h = this.get(HEIGHT),
            gap;
        if(legend && legend.get("includeInChartLayout"))
        {
            gap = legend.get("styles").gap;
            position = legend.get(POSITION);
            if(position !== EXTERNAL)
            {
                direction = legend.get("direction");
                dimension = direction === HORIZONTAL ? HEIGHT : WIDTH;
                size = legend.get(dimension);
                box[position] = size + gap;
                switch(position)
                {
                    case TOP :
                        legend.set(_Y, 0);
                    break;
                    case BOTTOM :
                        legend.set(_Y, h - size);
                    break;
                    case RIGHT :
                        legend.set(_X, w - size);
                    break;
                    case LEFT:
                        legend.set(_X, 0);
                    break;
                }
            }
        }
        return box;
    },

    /**
     * Destructor implementation for the CartesianChart class. Calls destroy on all axes, series, legend (if available) and the Graph instance.
     * Removes the tooltip and overlay HTML elements.
     *
     * @method destructor
     * @protected
     */
    destructor: function()
    {
        var legend = this.get("legend");
        if(legend)
        {
            legend.destroy(true);
        }
    }
}, {
    ATTRS: {
        legend: LEGEND
    }
});

Y.CartesianChart = CartesianChartLegend;

PieChartLegend = Y.Base.create("pieChartLegend", Y.PieChart, [], {
    /**
     * Redraws the chart instance.
     *
     * @method _redraw
     * @private
     */
    _redraw: function()
    {
        if(this._drawing)
        {
            this._callLater = true;
            return;
        }
        this._drawing = true;
        this._callLater = false;
        var graph = this.get("graph"),
            w = this.get("width"),
            h = this.get("height"),
            graphWidth,
            graphHeight,
            legend = this.get("legend"),
            x = 0,
            y = 0,
            legendX = 0,
            legendY = 0,
            legendWidth,
            legendHeight,
            dimension,
            gap,
            position,
            direction;
        if(graph)
        {
            if(legend)
            {
                position = legend.get("position");
                direction = legend.get("direction");
                graphWidth = graph.get("width");
                graphHeight = graph.get("height");
                legendWidth = legend.get("width");
                legendHeight = legend.get("height");
                gap = legend.get("styles").gap;

                if((direction === "vertical" && (graphWidth + legendWidth + gap !== w)) ||
                    (direction === "horizontal" &&  (graphHeight + legendHeight + gap !== h)))
                {
                    switch(legend.get("position"))
                    {
                        case LEFT :
                            dimension = Math.min(w - (legendWidth + gap), h);
                            legendHeight = h;
                            x = legendWidth + gap;
                            legend.set(HEIGHT, legendHeight);
                        break;
                        case TOP :
                            dimension = Math.min(h - (legendHeight + gap), w);
                            legendWidth = w;
                            y = legendHeight + gap;
                            legend.set(WIDTH, legendWidth);
                        break;
                        case RIGHT :
                            dimension = Math.min(w - (legendWidth + gap), h);
                            legendHeight = h;
                            legendX = dimension + gap;
                            legend.set(HEIGHT, legendHeight);
                        break;
                        case BOTTOM :
                            dimension = Math.min(h - (legendHeight + gap), w);
                            legendWidth = w;
                            legendY = dimension + gap;
                            legend.set(WIDTH, legendWidth);
                        break;
                    }
                    graph.set(WIDTH, dimension);
                    graph.set(HEIGHT, dimension);
                }
                else
                {
                    switch(legend.get("position"))
                    {
                        case LEFT :
                            x = legendWidth + gap;
                        break;
                        case TOP :
                            y = legendHeight + gap;
                        break;
                        case RIGHT :
                            legendX = graphWidth + gap;
                        break;
                        case BOTTOM :
                            legendY = graphHeight + gap;
                        break;
                    }
                }
            }
            else
            {
                graph.set(_X, 0);
                graph.set(_Y, 0);
                graph.set(WIDTH, w);
                graph.set(HEIGHT, h);
            }
        }
        this._drawing = false;
        if(this._callLater)
        {
            this._redraw();
            return;
        }
        if(graph)
        {
            graph.set(_X, x);
            graph.set(_Y, y);
        }
        if(legend)
        {
            legend.set(_X, legendX);
            legend.set(_Y, legendY);
        }
    }
}, {
    ATTRS: {
        /**
         * The legend for the chart.
         *
         * @attribute
         * @type Legend
         */
        legend: LEGEND
    }
});
Y.PieChart = PieChartLegend;
/**
 * ChartLegend provides a legend for a chart.
 *
 * @class ChartLegend
 * @module charts
 * @submodule charts-legend
 * @extends Widget
 */
Y.ChartLegend = Y.Base.create("chartlegend", Y.Widget, [Y.Renderer], {
    /**
     * Initializes the chart.
     *
     * @method initializer
     * @private
     */
    initializer: function()
    {
        this._items = [];
    },

    /**
     * @method renderUI
     * @private
     */
    renderUI: function()
    {
        var bb = this.get("boundingBox"),
            cb = this.get("contentBox"),
            styles = this.get("styles").background,
            background = new Y.Rect({
                graphic: cb,
                fill: styles.fill,
                stroke: styles.border
            });
        bb.setStyle("display", "block");
        bb.setStyle("position", "absolute");
        this.set("background", background);
    },

    /**
     * @method bindUI
     * @private
     */
    bindUI: function()
    {
        this.get("chart").after("seriesCollectionChange", Y.bind(this._updateHandler, this));
        this.get("chart").after("stylesChange", Y.bind(this._updateHandler, this));
        this.after("stylesChange", this._updateHandler);
        this.after("positionChange", this._positionChangeHandler);
        this.after("widthChange", this._handleSizeChange);
        this.after("heightChange", this._handleSizeChange);
    },

    /**
     * @method syncUI
     * @private
     */
    syncUI: function()
    {
        var w = this.get("width"),
            h = this.get("height");
        if(isFinite(w) && isFinite(h) && w > 0 && h > 0)
        {
            this._drawLegend();
        }
    },

    /**
     * Handles changes to legend.
     *
     * @method _updateHandler
     * @param {Object} e Event object
     * @private
     */
    _updateHandler: function()
    {
        if(this.get("rendered"))
        {
            this._drawLegend();
        }
    },

    /**
     * Handles position changes.
     *
     * @method _positionChangeHandler
     * @param {Object} e Event object
     * @private
     */
    _positionChangeHandler: function()
    {
        var chart = this.get("chart"),
            parentNode = this._parentNode;
        if(parentNode && ((chart && this.get("includeInChartLayout"))))
        {
            this.fire("legendRendered");
        }
        else if(this.get("rendered"))
        {
            this._drawLegend();
        }
    },

    /**
     * Updates the legend when the size changes.
     *
     * @method _handleSizeChange
     * @param {Object} e Event object.
     * @private
     */
    _handleSizeChange: function(e)
    {
        var attrName = e.attrName,
            pos = this.get(POSITION),
            vert = pos === LEFT || pos === RIGHT,
            hor = pos === BOTTOM || pos === TOP;
        if((hor && attrName === WIDTH) || (vert && attrName === HEIGHT))
        {
            this._drawLegend();
        }
    },

    /**
     * Draws the legend
     *
     * @method _drawLegend
     * @private
     */
    _drawLegend: function()
    {
        if(this._drawing)
        {
            this._callLater = true;
            return;
        }
        this._drawing = true;
        this._callLater = false;
        if(this.get("includeInChartLayout"))
        {
            this.get("chart")._itemRenderQueue.unshift(this);
        }
        var chart = this.get("chart"),
            node = this.get("contentBox"),
            seriesCollection = chart.get("seriesCollection"),
            series,
            styles = this.get("styles"),
            padding = styles.padding,
            itemStyles = styles.item,
            seriesStyles,
            hSpacing = itemStyles.hSpacing,
            vSpacing = itemStyles.vSpacing,
            direction = this.get("direction"),
            align = direction === "vertical" ? styles.vAlign : styles.hAlign,
            marker = styles.marker,
            labelStyles = itemStyles.label,
            displayName,
            layout = this._layout[direction],
            i,
            len,
            isArray,
            legendShape,
            shape,
            shapeClass,
            item,
            fill,
            border,
            fillColors,
            borderColors,
            borderWeight,
            items = [],
            markerWidth = marker.width,
            markerHeight = marker.height,
            totalWidth = 0 - hSpacing,
            totalHeight = 0 - vSpacing,
            maxWidth = 0,
            maxHeight = 0,
            itemWidth,
            itemHeight;
        if(marker && marker.shape)
        {
            legendShape = marker.shape;
        }
        this._destroyLegendItems();
        if(chart instanceof Y.PieChart)
        {
            series = seriesCollection[0];
            displayName = series.get("categoryAxis").getDataByKey(series.get("categoryKey"));
            seriesStyles = series.get("styles").marker;
            fillColors = seriesStyles.fill.colors;
            borderColors = seriesStyles.border.colors;
            borderWeight = seriesStyles.border.weight;
            i = 0;
            len = displayName.length;
            shape = legendShape || Y.Circle;
            isArray = Y.Lang.isArray(shape);
            for(; i < len; ++i)
            {
                shape = isArray ? shape[i] : shape;
                fill = {
                    color: fillColors[i]
                };
                border = {
                    colors: borderColors[i],
                    weight: borderWeight
                };
                displayName = chart.getSeriesItems(series, i).category.value;
                item = this._getLegendItem(node, this._getShapeClass(shape), fill, border, labelStyles, markerWidth, markerHeight, displayName);
                itemWidth = item.width;
                itemHeight = item.height;
                maxWidth = Math.max(maxWidth, itemWidth);
                maxHeight = Math.max(maxHeight, itemHeight);
                totalWidth += itemWidth + hSpacing;
                totalHeight += itemHeight + vSpacing;
                items.push(item);
            }
        }
        else
        {
            i = 0;
            len = seriesCollection.length;
            for(; i < len; ++i)
            {
                series = seriesCollection[i];
                seriesStyles = this._getStylesBySeriesType(series, shape);
                if(!legendShape)
                {
                    shape = seriesStyles.shape;
                    if(!shape)
                    {
                        shape = Y.Circle;
                    }
                }
                shapeClass = Y.Lang.isArray(shape) ? shape[i] : shape;
                item = this._getLegendItem(
                    node,
                    this._getShapeClass(shape),
                    seriesStyles.fill,
                    seriesStyles.border,
                    labelStyles,
                    markerWidth,
                    markerHeight,
                    series.get("valueDisplayName")
                );
                itemWidth = item.width;
                itemHeight = item.height;
                maxWidth = Math.max(maxWidth, itemWidth);
                maxHeight = Math.max(maxHeight, itemHeight);
                totalWidth += itemWidth + hSpacing;
                totalHeight += itemHeight + vSpacing;
                items.push(item);
            }
        }
        this._drawing = false;
        if(this._callLater)
        {
            this._drawLegend();
        }
        else
        {
            layout._positionLegendItems.apply(
                this,
                [items, maxWidth, maxHeight, totalWidth, totalHeight, padding, hSpacing, vSpacing, align]
            );
            this._updateBackground(styles);
            this.fire("legendRendered");
        }
    },

    /**
     * Updates the background for the legend.
     *
     * @method _updateBackground
     * @param {Object} styles Reference to the legend's styles attribute
     * @private
     */
    _updateBackground: function(styles)
    {
        var backgroundStyles = styles.background,
            contentRect = this._contentRect,
            padding = styles.padding,
            x = contentRect.left - padding.left,
            y = contentRect.top - padding.top,
            w = contentRect.right - x + padding.right,
            h = contentRect.bottom - y + padding.bottom;
        this.get("background").set({
            fill: backgroundStyles.fill,
            stroke: backgroundStyles.border,
            width: w,
            height: h,
            x: x,
            y: y
        });
    },

    /**
     * Retrieves the marker styles based on the type of series. For series that contain a marker, the marker styles are returned.
     *
     * @method _getStylesBySeriesType
     * @param {CartesianSeries | PieSeries} The series in which the style properties will be received.
     * @return Object An object containing fill, border and shape information.
     * @private
     */
    _getStylesBySeriesType: function(series)
    {
        var styles = series.get("styles"),
            color;
        if(series instanceof Y.LineSeries || series instanceof Y.StackedLineSeries)
        {
            styles = series.get("styles").line;
            color = styles.color || series._getDefaultColor(series.get("graphOrder"), "line");
            return {
                border: {
                    weight: 1,
                    color: color
                },
                fill: {
                    color: color
                }
            };
        }
        else if(series instanceof Y.AreaSeries || series instanceof Y.StackedAreaSeries)
        {
            styles = series.get("styles").area;
            color = styles.color || series._getDefaultColor(series.get("graphOrder"), "slice");
            return {
                border: {
                    weight: 1,
                    color: color
                },
                fill: {
                    color: color
                }
            };
        }
        else
        {
            styles = series.get("styles").marker;
            return {
                fill: styles.fill,

                border: {
                    weight: styles.border.weight,

                    color: styles.border.color,

                    shape: styles.shape
                },
                shape: styles.shape
            };
        }
    },

    /**
     * Returns a legend item consisting of the following properties:
     *  <dl>
     *    <dt>node</dt><dd>The `Node` containing the legend item elements.</dd>
     *      <dt>shape</dt><dd>The `Shape` element for the legend item.</dd>
     *      <dt>textNode</dt><dd>The `Node` containing the text></dd>
     *      <dt>text</dt><dd></dd>
     *  </dl>
     *
     * @method _getLegendItem
     * @param {Node} shapeProps Reference to the `node` attribute.
     * @param {String | Class} shapeClass The type of shape
     * @param {Object} fill Properties for the shape's fill
     * @param {Object} border Properties for the shape's border
     * @param {String} labelStyles String to be rendered as the legend's text
     * @param {Number} width Total width of the legend item
     * @param {Number} height Total height of the legend item
     * @param {String} text Text for the legendItem
     * @return Object
     * @private
     */
    _getLegendItem: function(node, shapeClass, fill, border, labelStyles, w, h, text)
    {
        var containerNode = Y.Node.create("<div>"),
            textField = Y.Node.create("<span>"),
            shape,
            dimension,
            padding,
            left,
            item,
            ShapeClass = shapeClass;
        containerNode.setStyle(POSITION, "absolute");
        textField.setStyle(POSITION, "absolute");
        textField.setStyles(labelStyles);
        textField.set("text", text);
        containerNode.appendChild(textField);
        node.append(containerNode);
        dimension = textField.get("offsetHeight");
        padding = dimension - h;
        left = w + padding + 2;
        textField.setStyle("left", left + PX);
        containerNode.setStyle("height", dimension + PX);
        containerNode.setStyle("width", (left + textField.get("offsetWidth")) + PX);
        shape = new ShapeClass({
            fill: fill,
            stroke: border,
            width: w,
            height: h,
            x: padding * 0.5,
            y: padding * 0.5,
            w: w,
            h: h,
            graphic: containerNode
        });
        textField.setStyle("left", dimension + PX);
        item = {
            node: containerNode,
            width: containerNode.get("offsetWidth"),
            height: containerNode.get("offsetHeight"),
            shape: shape,
            textNode: textField,
            text: text
        };
        this._items.push(item);
        return item;
    },

    /**
     * Evaluates and returns correct class for drawing a shape.
     *
     * @method _getShapeClass
     * @return Shape
     * @private
     */
    _getShapeClass: function()
    {
        var graphic = this.get("background").get("graphic");
        return graphic._getShapeClass.apply(graphic, arguments);
    },

    /**
     * Returns the default hash for the `styles` attribute.
     *
     * @method _getDefaultStyles
     * @return Object
     * @protected
     */
    _getDefaultStyles: function()
    {
        var styles = {
            padding: {
                top: 8,
                right: 8,
                bottom: 8,
                left: 9
            },
            gap: 10,
            hAlign: "center",
            vAlign: "top",
            marker: this._getPlotDefaults(),
            item: {
                hSpacing: 10,
                vSpacing: 5,
                label: {
                    color:"#808080",
                    fontSize:"85%",
                    whiteSpace: "nowrap"
                }
            },
            background: {
                shape: "rect",
                fill:{
                    color:"#faf9f2"
                },
                border: {
                    color:"#dad8c9",
                    weight: 1
                }
            }
        };
        return styles;
    },

    /**
     * Gets the default values for series that use the utility. This method is used by
     * the class' `styles` attribute's getter to get build default values.
     *
     * @method _getPlotDefaults
     * @return Object
     * @protected
     */
    _getPlotDefaults: function()
    {
        var defs = {
            width: 10,
            height: 10
        };
        return defs;
    },

    /**
     * Destroys legend items.
     *
     * @method _destroyLegendItems
     * @private
     */
    _destroyLegendItems: function()
    {
        var item;
        if(this._items)
        {
            while(this._items.length > 0)
            {
                item = this._items.shift();
                item.shape.get("graphic").destroy();
                item.node.empty();
                item.node.destroy(true);
                item.node = null;
                item = null;
            }
        }
        this._items = [];
    },

    /**
     * Maps layout classes.
     *
     * @property _layout
     * @private
     */
    _layout: {
        vertical: VerticalLegendLayout,
        horizontal: HorizontalLegendLayout
    },

    /**
     * Destructor implementation ChartLegend class. Removes all items and the Graphic instance from the widget.
     *
     * @method destructor
     * @protected
     */
    destructor: function()
    {
        var background = this.get("background"),
            backgroundGraphic;
        this._destroyLegendItems();
        if(background)
        {
            backgroundGraphic = background.get("graphic");
            if(backgroundGraphic)
            {
                backgroundGraphic.destroy();
            }
            else
            {
                background.destroy();
            }
        }

    }
}, {
    ATTRS: {
        /**
         * Indicates whether the chart's contentBox is the parentNode for the legend.
         *
         * @attribute includeInChartLayout
         * @type Boolean
         * @private
         */
        includeInChartLayout: {
            value: false
        },

        /**
         * Reference to the `Chart` instance.
         *
         * @attribute chart
         * @type Chart
         */
        chart: {
            setter: function(val)
            {
                this.after("legendRendered", Y.bind(val._itemRendered, val));
                return val;
            }
        },

        /**
         * Indicates the direction in relation of the legend's layout. The `direction` of the legend is determined by its
         * `position` value.
         *
         * @attribute direction
         * @type String
         */
        direction: {
            value: "vertical"
        },

        /**
         * Indicates the position and direction of the legend. Possible values are `left`, `top`, `right` and `bottom`.
         * Values of `left` and `right` values have a `direction` of `vertical`. Values of `top` and `bottom` values have
         * a `direction` of `horizontal`.
         *
         * @attribute position
         * @type String
         */
        position: {
            lazyAdd: false,

            value: "right",

            setter: function(val)
            {
                if(val === TOP || val === BOTTOM)
                {
                    this.set("direction", HORIZONTAL);
                }
                else if(val === LEFT || val === RIGHT)
                {
                    this.set("direction", VERTICAL);
                }
                return val;
            }
        },

        /**
         * The width of the legend. Depending on the implementation of the ChartLegend, this value is `readOnly`.
         * By default, the legend is included in the layout of the `Chart` that it references. Under this circumstance,
         * `width` is always `readOnly`. When the legend is rendered in its own dom element, the `readOnly` status is
         * determined by the direction of the legend. If the `position` is `left` or `right` or the `direction` is
         * `vertical`, width is `readOnly`. If the position is `top` or `bottom` or the `direction` is `horizontal`,
         * width can be explicitly set. If width is not explicitly set, the width will be determined by the width of the
         * legend's parent element.
         *
         * @attribute width
         * @type Number
         */
        width: {
            getter: function()
            {
                var chart = this.get("chart"),
                    parentNode = this._parentNode;
                if(parentNode)
                {
                    if((chart && this.get("includeInChartLayout")) || this._width)
                    {
                        if(!this._width)
                        {
                            this._width = 0;
                        }
                        return this._width;
                    }
                    else
                    {
                        return parentNode.get("offsetWidth");
                    }
                }
                return "";
            },

            setter: function(val)
            {
                this._width = val;
                return val;
            }
        },

        /**
         * The height of the legend. Depending on the implementation of the ChartLegend, this value is `readOnly`.
         * By default, the legend is included in the layout of the `Chart` that it references. Under this circumstance,
         * `height` is always `readOnly`. When the legend is rendered in its own dom element, the `readOnly` status is
         * determined by the direction of the legend. If the `position` is `top` or `bottom` or the `direction` is
         * `horizontal`, height is `readOnly`. If the position is `left` or `right` or the `direction` is `vertical`,
         * height can be explicitly set. If height is not explicitly set, the height will be determined by the width of the
         * legend's parent element.
         *
         * @attribute height
         * @type Number
         */
        height: {
            valueFn: "_heightGetter",

            getter: function()
            {
                var chart = this.get("chart"),
                    parentNode = this._parentNode;
                if(parentNode)
                {
                    if((chart && this.get("includeInChartLayout")) || this._height)
                    {
                        if(!this._height)
                        {
                            this._height = 0;
                        }
                        return this._height;
                    }
                    else
                    {
                        return parentNode.get("offsetHeight");
                    }
                }
                return "";
            },

            setter: function(val)
            {
                this._height = val;
                return val;
            }
        },

        /**
         * Indicates the x position of legend.
         *
         * @attribute x
         * @type Number
         * @readOnly
         */
        x: {
            lazyAdd: false,

            value: 0,

            setter: function(val)
            {
                var node = this.get("boundingBox");
                if(node)
                {
                    node.setStyle(LEFT, val + PX);
                }
                return val;
            }
        },

        /**
         * Indicates the y position of legend.
         *
         * @attribute y
         * @type Number
         * @readOnly
         */
        y: {
            lazyAdd: false,

            value: 0,

            setter: function(val)
            {
                var node = this.get("boundingBox");
                if(node)
                {
                    node.setStyle(TOP, val + PX);
                }
                return val;
            }
        },

        /**
         * Array of items contained in the legend. Each item is an object containing the following properties:
         *
         * <dl>
         *      <dt>node</dt><dd>Node containing text for the legend item.</dd>
         *      <dt>marker</dt><dd>Shape for the legend item.</dd>
         * </dl>
         *
         * @attribute items
         * @type Array
         * @readOnly
         */
        items: {
            getter: function()
            {
                return this._items;
            }
        },

        /**
         * Background for the legend.
         *
         * @attribute background
         * @type Rect
         */
        background: {}

        /**
         * Properties used to display and style the ChartLegend.  This attribute is inherited from `Renderer`.
         * Below are the default values:
         *
         *  <dl>
         *      <dt>gap</dt><dd>Distance, in pixels, between the `ChartLegend` instance and the chart's content. When `ChartLegend`
         *      is rendered within a `Chart` instance this value is applied.</dd>
         *      <dt>hAlign</dt><dd>Defines the horizontal alignment of the `items` in a `ChartLegend` rendered in a horizontal direction.
         *      This value is applied when the instance's `position` is set to top or bottom. This attribute can be set to left, center
         *      or right. The default value is center.</dd>
         *      <dt>vAlign</dt><dd>Defines the vertical alignment of the `items` in a `ChartLegend` rendered in vertical direction. This
         *      value is applied when the instance's `position` is set to left or right. The attribute can be set to top, middle or
         *      bottom. The default value is middle.</dd>
         *      <dt>item</dt><dd>Set of style properties applied to the `items` of the `ChartLegend`.
         *          <dl>
         *              <dt>hSpacing</dt><dd>Horizontal distance, in pixels, between legend `items`.</dd>
         *              <dt>vSpacing</dt><dd>Vertical distance, in pixels, between legend `items`.</dd>
         *              <dt>label</dt><dd>Properties for the text of an `item`.
         *                  <dl>
         *                      <dt>color</dt><dd>Color of the text. The default values is "#808080".</dd>
         *                      <dt>fontSize</dt><dd>Font size for the text. The default value is "85%".</dd>
         *                  </dl>
         *              </dd>
         *              <dt>marker</dt><dd>Properties for the `item` markers.
         *                  <dl>
         *                      <dt>width</dt><dd>Specifies the width of the markers.</dd>
         *                      <dt>height</dt><dd>Specifies the height of the markers.</dd>
         *                  </dl>
         *              </dd>
         *          </dl>
         *      </dd>
         *      <dt>background</dt><dd>Properties for the `ChartLegend` background.
         *          <dl>
         *              <dt>fill</dt><dd>Properties for the background fill.
         *                  <dl>
         *                      <dt>color</dt><dd>Color for the fill. The default value is "#faf9f2".</dd>
         *                  </dl>
         *              </dd>
         *              <dt>border</dt><dd>Properties for the background border.
         *                  <dl>
         *                      <dt>color</dt><dd>Color for the border. The default value is "#dad8c9".</dd>
         *                      <dt>weight</dt><dd>Weight of the border. The default values is 1.</dd>
         *                  </dl>
         *              </dd>
         *          </dl>
         *      </dd>
         * </dl>
         *
         * @attribute styles
         * @type Object
         */
    }
});


}, '3.17.2', {"requires": ["charts-base"]});
